package com.xcity.game.land;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xcity.db.entity.Land;
import com.xcity.db.entity.Player;
import com.xcity.db.mapper.LandMapper;
import com.xcity.game.ISystem;
import com.xcity.game.common.Cause;
import com.xcity.game.common.Currency;
import com.xcity.game.common.MessageConst;
import com.xcity.game.event.GameEvents;
import com.xcity.game.mail.MailService;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.request.BuyLandRequest;
import com.xcity.pomelo.request.GetLandListRequest;
import com.xcity.pomelo.request.TakeMoneyOfLandRequest;
import com.xcity.pomelo.response.BuyLandResponse;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.LandListResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.util.DBUtils;

import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.procedure.TObjectProcedure;
import naga.x.App;
import naga.x.game.template.TemplateService;
import naga.x.game.time.DayListener;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.TimeUtils;

@OPHandler
public class LandService implements Service, DayListener, ISystem {

	private TIntObjectMap<Land> lands;
	
	public static final int BUY_LAND_DEC_STR = 10; // 购买地标消耗10点体力
	// 凌晨0点到7点不能收购地标，凌晨1点清空地标
	public static final int ILLEGAL_TIME_BEGIN = 0;//(int) TimeUnit.HOURS.toMillis(1);
	public static final int ILLEGAL_TIME_END = (int) TimeUnit.HOURS.toMillis(7);
	public static final int RECYCLE_TIME = (int) TimeUnit.HOURS.toMillis(1);
	// UTC time
	private long illegalTimeBegin, illegalTimeEnd;
	private ScheduledFuture<?> recycleTaskFuture;
	private RecycleTask recycleTask = new RecycleTask();
	
	class RecycleTask implements Runnable {
		@Override
		public void run() {
			lands.forEachValue(new TObjectProcedure<Land>() {
				@Override
				public boolean execute(Land land) {
					land.recycle();
					LOG.info("[LAND RECYCLE]ID[{}]", land.getId());
					return true;
				}
			});
		}
	}
	
	protected static final Logger LOG = LoggerFactory.getLogger(LandService.class);
	
	@Override
	public void startup() throws ServiceException {
		TemplateService ts = App.getApp().getServiceManager().get(TemplateService.class);
		TIntObjectMap<LandTemplate> templates = ts.getTemplates(LandTemplate.class);
		lands = new TIntObjectHashMap<Land>(templates.size());
		List<Land> landList = DBUtils.findAll(LandMapper.class);
		if (landList != null && landList.size() > 0) {
			for (Land land : landList) {
				lands.put(land.getId(), land);
			}
		}
		if (lands.size() < templates.size()) {
			Collection<LandTemplate> vals = templates.valueCollection();
			List<Land> newCreates = new ArrayList<Land>(6);
			for (LandTemplate t : vals) {
				if (!lands.containsKey(t.getId())) {
					// new
					Land land = new Land();
					land.setId(t.getId());
					lands.put(land.getId(), land);
					newCreates.add(land);
				}
			}
			DBUtils.batchSave(LandMapper.class, newCreates);
		}
		dayChanged();
		TimeUpdater.getInstance().addDayListener(this);
	}
	
	@Override
	public void shutdown() throws ServiceException {
		DBUtils.batchUpdate(LandMapper.class, lands.valueCollection());
	}

	@Override
	public String getId() {
		return getClass().getName();
	}
	
	public List<Land> getPlayerLands(long playerId) {
		Collection<Land> vals = lands.valueCollection();
		List<Land> result = new ArrayList<Land>(vals.size());
		for (Land land : vals) {
			if (playerId == land.getOwnerId()) {
				result.add(land);
			}
		}
		return result;
	}
	
	@PomeloOP(PomeloRoute.LIST_LAND)
	protected void requestLandList(Session session, GetLandListRequest req) {
		session.send(new LandListResponse(req.getSerial(), lands.valueCollection()));
	}
	
	@PomeloOP(PomeloRoute.BUY_LAND)
	protected void requestBuyLand(Session session, BuyLandRequest req) {
		Land land = lands.get(req.getLandId());
		if (land == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		if (land.getOwnerId() == player.getId()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_OWNED));
			return;
		}
		if (land.getCD() > 0) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_IN_CD));
			return;
		}
		if (inIllegalTime()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_BUY_IN_ILLEGAL_TIME));
			return;
		}
		int level = land.getLevel() + 1;
		if (level > land.getTemplate().getBuyTemplates().size()) {
			level = 1;
		}
		if (!player.isEnough(Currency.STRENGTH, BUY_LAND_DEC_STR)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.STR_NOT_ENOUGH));
			return;
		}
		int cost = land.getTemplate().getBuyTemplate(level).getCost();
		Currency currency = Currency.forCode(land.getTemplate().getCostType());
		if (!player.isEnough(currency, cost)) {
			session.send(new HintResponse(req.getSerial(), currency == Currency.GOLD ? MessageConst.GOLD_NOT_ENOUGH : MessageConst.STONE_NOT_ENOUTH));
			return;
		}
		if (!player.cost(Currency.STRENGTH, BUY_LAND_DEC_STR, Cause.BUY_LAND)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.STR_NOT_ENOUGH));
			return;
		}
		if (!player.cost(currency, cost, Cause.BUY_LAND)) {
			session.send(new HintResponse(req.getSerial(), currency == Currency.GOLD ? MessageConst.GOLD_NOT_ENOUGH : MessageConst.STONE_NOT_ENOUTH));
			return;
		}
		if (land.hasOwner()) {
			App.getApp().getServiceManager().get(MailService.class).sendSystemMail(
					land.getOwnerId(),
					MessageFormat.format(MessageConst.LAND_BE_ACQUIRED_MAIL_TITLE, land.getTemplate().getName(), player.getName()),
					MessageFormat.format(MessageConst.LAND_BE_ACQUIRED_MAIL_CONTENT, player.getName(), cost, currency.name, land.getTemplate().getName()));
		}
		land.changeOwner(player);
		session.send(new BuyLandResponse(req.getSerial(), land, player));
		App.getApp().getEventManager().addEvent(GameEvents.EVENT_BUY_LAND, player, land);
		App.getApp().getEventManager().addEvent(GameEvents.EVENT_USE_SYSTEM, player, getSystemId());
	}
	
	@PomeloOP(PomeloRoute.TAKE_LAND_MONEY)
	protected void requestTakeMoney(Session session, TakeMoneyOfLandRequest req) {
		Land land = lands.get(req.getLandId());
		if (land == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		if (land.getOwnerId() != player.getId()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.LAND_NOT_OWN));
			return;
		}
		long money = 0;
		if (TimeUpdater.getInstance().now() - land.getLastTakeMoneyTime().getTime() < 2000 || (money = land.calcMoney()) <= 0) {
//			session.send(new HintResponse(req.getSerial(), MessageConst.BUILD_TAKE_MONEY_TIME_LIMIT)); // do not tip
			session.send(new Response(req.getSerial()));
			return;
		}
		land.getLastTakeMoneyTime().setTime(TimeUpdater.getInstance().now());
		player.addGold(money, Cause.LAND_INCOME);
//		session.send(new BuildTakeMoneyResponse(req.getSerial(), player));
		session.send(new Response(req.getSerial()));
	}

	@Override
	public boolean dayChanged() {
		long zeroTime = TimeUtils.getTodayZeroTime();
		illegalTimeBegin = zeroTime + ILLEGAL_TIME_BEGIN;
		illegalTimeEnd = zeroTime + ILLEGAL_TIME_END;
		long delay = zeroTime + RECYCLE_TIME - TimeUpdater.getInstance().now();
		if (delay >= 0) {
			if (recycleTaskFuture != null && !recycleTaskFuture.isDone() && !recycleTaskFuture.isCancelled()) {
				recycleTaskFuture.cancel(true);
			}
			recycleTaskFuture = App.getApp().getSchedule().schedule(recycleTask, delay, TimeUnit.MILLISECONDS);
			LOG.info("[LAND RECYCLE]SCHEDULE[" + delay + "]");
		} else {
			LOG.info("[LAND RECYCLE]SCHEDULE CANCEL");
		}
		return true;
	}
	
	protected boolean inIllegalTime() {
		long now = TimeUpdater.getInstance().now();
		return now >= illegalTimeBegin && now < illegalTimeEnd;
	}

	@Override
	public int getSystemId() {
		return SYS_LAND;
	}

}
